//-----------------------------------------------------------------------------
//  Copyright (C) 2011-2018, GB Research, LLC (www.gbresearch.com)
//  
//  Boost Software License - Version 1.0 - August 17th, 2003
//
//  Permission is hereby granted, free of charge, to any person or organization
//  obtaining a copy of the software and accompanying documentation covered by
//  this license (the "Software") to use, reproduce, display, distribute,
//  execute, and transmit the Software, and to prepare derivative works of the
//  Software, and to permit third-parties to whom the Software is furnished to
//  do so, all subject to the following:
//
//  The copyright notices in the Software and this entire statement, including
//  the above license grant, this restriction and the following disclaimer,
//  must be included in all copies of the Software, in whole or in part, and
//  all derivative works of the Software, unless such copies or derivative
//  works are solely in the form of machine-executable object code generated by
//  a source language processor.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
//  SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
//  FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
//  ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#pragma once

#include <type_traits>
#include <utility>
#include <string>
#include "axe_composite.h"
#include "axe_terminal.h"
#include "axe_trait.h"

namespace axe
{
    namespace detail
    {
        //-------------------------------------------------------------------------
        /// r_lit_traits traits template for literals
        //-------------------------------------------------------------------------
        template<class T, bool = std::is_arithmetic_v<typename std::decay_t<T>>>
        struct r_lit_traits {};

        template<class T>
        struct r_lit_traits<T, true> { using type = r_bin<T>; };

        template<>
        struct r_lit_traits<char> { using type = r_char<char>; };

        template<>
        struct r_lit_traits<wchar_t> { using type = r_char<wchar_t>; };

        template<>
        struct r_lit_traits<const char*> { using type = r_str<char>; };

        template<>
        struct r_lit_traits<const wchar_t*> { using type = r_str<wchar_t>; };

        template<class T>
        using r_lit_traits_t = typename r_lit_traits<T, std::is_arithmetic_v<std::decay_t<T>>>::type;

        //-------------------------------------------------------------------------
        /// r_val_traits template for value class types
        //-------------------------------------------------------------------------
        template<class T, bool = !AXE_IS_RULE(T) && std::is_class_v<std::decay_t<T>>>
        struct r_val_traits {};

        template<class CharT, class TraitsT, class AllocT>
        struct r_val_traits<std::basic_string<CharT, TraitsT, AllocT>&, true>
        {
            using type = r_str<std::basic_string<CharT, TraitsT, AllocT>>;
        };

        template<class CharT, class TraitsT, class AllocT>
        struct r_val_traits<std::basic_string<CharT, TraitsT, AllocT>, true>
        {
            using type = r_str<std::basic_string<CharT, TraitsT, AllocT>>;
        };
    }

    //-------------------------------------------------------------------------
    /// r_many rule for literals
    //-------------------------------------------------------------------------
    template<class R1, class R2>
    r_many_t<
        std::enable_if_t<AXE_IS_RULE(R1), R1>,
        std::enable_if_t<!AXE_IS_RULE(R2)
        // avoid overload ambiguity between r_many(r, 0) and r_many(r, detail::r_lit_traits<int>(0))
        && (!std::is_convertible_v<R2, size_t>| std::is_same_v<R2, char> | std::is_same_v<R2, wchar_t>),
        detail::r_lit_traits_t<R2>>
    >
        r_many(R1&& r1, R2 r2, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<R1, detail::r_lit_traits_t<R2>>(std::forward<R1>(r1),
            static_cast<detail::r_lit_traits_t<R2>>(r2), min_occurrence, max_occurrence);
    }
    //-------------------------------
    template<class R1, class R2>
    r_many_t<
        detail::r_lit_traits_t<R1>,
        std::enable_if_t<AXE_IS_RULE(R2), R2>
    >
        r_many(R1 r1, R2&& r2, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<detail::r_lit_traits_t<R1>, R2>(detail::r_lit_traits_t<R1>(r1),
            std::forward<R2>(r2), min_occurrence, max_occurrence);
    }

    //-------------------------------
    template<class R1, class R2>
    r_many_t<
        detail::r_lit_traits_t<R1>,
        detail::r_lit_traits_t<R2>
    >
        r_many(R1 r1, R2 r2, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<detail::r_lit_traits_t<R1>, detail::r_lit_traits_t<R2>>(
            detail::r_lit_traits_t<R1>(r1),
            detail::r_lit_traits_t<R1>(r2), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_many rule for literals
    //-------------------------------------------------------------------------
    template<class R>
    r_many_t<
        std::enable_if_t<!AXE_IS_RULE(R), detail::r_lit_traits_t<R>>,
        r_empty
    >
        r_many(R r, size_t min_occurrence = 1, size_t max_occurrence = -1)
    {
        return r_many_t<detail::r_lit_traits_t<R>, r_empty>(
            static_cast<detail::r_lit_traits_t<R>>(r), r_empty(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_lit rule matches specified literal
    //-------------------------------------------------------------------------
    template<class T>
    inline
        std::enable_if_t<!AXE_IS_RULE(T), detail::r_lit_traits_t<T>>
        r_lit(T t) { return static_cast<detail::r_lit_traits_t<T>>(t); }

    //-------------------------------------------------------------------------
    /// r_alpha rule matches single alpha character (A-Z,a-z,_)
    //-------------------------------------------------------------------------
    inline r_pred<is_alpha> r_alpha() { return r_pred(is_alpha()); }

    //-------------------------------------------------------------------------
    /// r_alphastr rule marches a string of alpha characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alpha> r_alphastr() { return r_predstr(is_alpha()); }

    //-------------------------------------------------------------------------
    /// r_alphastr rule marches a string of alpha characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alpha, true> r_alphastr(size_t occurrence)
    {
        return r_predstr(is_alpha(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_alphastr rule marches a string of alpha characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alpha, true> r_alphastr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_alpha(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_num rule matches a single numeric character (0-9)
    //-------------------------------------------------------------------------
    inline r_pred<is_num> r_num() { return r_pred(is_num()); }

    //-------------------------------------------------------------------------
    /// r_numstr rule matches a string of numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_num> r_numstr() { return r_predstr(is_num()); }

    //-------------------------------------------------------------------------
    /// r_numstr rule matches a string of numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_num, true> r_numstr(size_t occurrence)
    {
        return r_predstr(is_num(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_numstr rule matches a string of numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_num, true> r_numstr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_num(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_alnum rule matches a single alpha-numeric character
    //-------------------------------------------------------------------------
    inline r_pred<is_alnum> r_alnum() { return r_pred(is_alnum()); }

    //-------------------------------------------------------------------------
    /// r_alnumstr matches a string of alpha-numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alnum> r_alnumstr() { return r_predstr(is_alnum()); }

    //-------------------------------------------------------------------------
    /// r_alnumstr matches a string of alpha-numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alnum, true> r_alnumstr(size_t occurrence)
    {
        return r_predstr(is_alnum(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_alnumstr matches a string of alpha-numeric characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_alnum, true> r_alnumstr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_alnum(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_oct rule matches a single octadecimal character (0-7)
    //-------------------------------------------------------------------------
    inline r_pred<is_oct> r_oct() { return r_pred(is_oct()); }

    //-------------------------------------------------------------------------
    /// r_octstr rule matches a string of octadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_oct> r_octstr() { return r_predstr(is_oct()); }

    //-------------------------------------------------------------------------
    /// r_octstr rule matches a string of octadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_oct, true> r_octstr(size_t occurrence)
    {
        return r_predstr(is_oct(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_octstr rule matches a string of octadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_oct, true> r_octstr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_oct(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_hex rule matches a single hexadecimal character (0-9,a-f,A-F)
    //-------------------------------------------------------------------------
    inline r_pred<is_hex> r_hex() { return r_pred(is_hex()); }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_hex> r_hexstr() { return r_predstr(is_hex()); }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_hex, true> r_hexstr(size_t occurrence)
    {
        return r_predstr(is_hex(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_hex, true> r_hexstr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_hex(), min_occurrence, max_occurrence);
    }


    //-------------------------------------------------------------------------
    /// r_binary rule matches a single binary character (0,1)
    //-------------------------------------------------------------------------
    inline r_pred<is_bin> r_binary() { return r_pred(is_bin()); }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_bin> r_binstr() { return r_predstr(is_bin()); }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_bin, true> r_binstr(size_t occurrence)
    {
        return r_predstr(is_bin(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_hexstr rule matches a string of hexadecimal characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_bin, true> r_binstr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_bin(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_printable rule matches a single printable character
    //-------------------------------------------------------------------------
    inline r_pred<is_printable> r_printable() { return r_pred(is_printable()); }

    //-------------------------------------------------------------------------
    /// r_printablestr rule matches a string of printable characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_printable> r_printablestr() { return r_predstr(is_printable()); }

    //-------------------------------------------------------------------------
    /// r_printablestr rule matches a string of printable characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_printable, true> r_printablestr(size_t occurrence)
    {
        return r_predstr(is_printable(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_printablestr rule matches a string of printable characters
    //-------------------------------------------------------------------------
    inline r_predstr<is_printable, true> r_printablestr(size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_printable(), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_any rule matches a single character in the range [from, to]
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_pred<is_any_t<CharT>> r_any(CharT from, CharT to)
    {
        return r_pred(is_any_t<CharT>(from, to));
    }

    //-------------------------------------------------------------------------
    /// r_any rule matches a single character present in the specified string
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_pred<is_any_t<const CharT*>> r_any(const CharT* str)
    {
        return r_pred(is_any_t<const CharT*>(str));
    }

    //-------------------------------------------------------------------------
    /// r_any rule matches any single character
    //-------------------------------------------------------------------------
    inline r_pred<is_any_t<void>> r_any() { return r_pred(is_any_t<void>()); }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters in the range [from, to]
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<CharT>> r_anystr(CharT from, CharT to)
    {
        return r_predstr(is_any_t<CharT>(from, to));
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of any characters specified length
    //-------------------------------------------------------------------------
    inline r_predstr<is_any_t<void>, true> r_anystr(size_t occurrence)
    {
        return r_predstr(is_any_t<void>(), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters in the range [from, to]
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<CharT>, true> r_anystr(CharT from, CharT to, size_t occurrence)
    {
        return r_predstr(is_any_t<CharT>(from, to), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters in the range [from, to]
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<CharT>, true> r_anystr(CharT from, CharT to, size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_any_t<CharT>(from, to), min_occurrence, max_occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters present in the specified string
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<const CharT*>> r_anystr(const CharT* str)
    {
        return r_predstr(is_any_t<const CharT*>(str));
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters present in the specified string
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<const CharT*>, true> r_anystr(const CharT* str, size_t occurrence)
    {
        return r_predstr(is_any_t<const CharT*>(str), occurrence);
    }

    //-------------------------------------------------------------------------
    /// r_anystr rule matches a string of characters present in the specified string
    //-------------------------------------------------------------------------
    template<class CharT>
    inline r_predstr<is_any_t<const CharT*>, true> r_anystr(const CharT* str, size_t min_occurrence, size_t max_occurrence)
    {
        return r_predstr(is_any_t<const CharT*>(str), min_occurrence, max_occurrence);
    }
}
